# ModelCache to use on an ActiveRecord class to cache method calls. Example:
#
#   Class X < ActiveRecord::Base
#     def heavy_calculation(a, b)
#       "result #{self.x} #{a} #{b}"
#     end
#
#     include ModelCacheHelper
#     cache_method :heavy_calculation
#   end
# 
# The created method X.heavy_calculation(id, arg1, arg2) will then check if the
# result is cached, otherwise it will do X.find(1).heavy_calculation(arg1, arg2) 
# and cache the result. Calling heavy_calculation on an instance of X will also
# cache the result.
#
# The instance method expire_cache, will expire the cache for that object. It 
# can be used with an optional argument, expire_cache(method), to only expire 
# the cache for a certain method.
#
# Non-string result will be serialized with YAML and automatically deserialized
# when loaded.
#
# The extra argument kan be used to define extra parameters to the find-request.
#
#  cache_method :heavy_calculation, :include => "content_items"

module ModelCacheHelper
  def self.included(klass)
    klass.extend ClassMethods
  end
 
  module ClassMethods  
    def check_cache(url, args)
      c = ActionController::Base.new
      return yield unless c.perform_caching
      
      url += args.map do |a|
        [String,Fixnum,Symbol,TrueClass,FalseClass].member?(a.class) ? a.to_s : nil
      end.join("-")

      unless f = c.read_fragment(url)
        res = yield
        c.write_fragment(url, res.is_a?(String) ? res : res.to_yaml)
        res
      else
        (f[0..3] == "--- ") ? YAML.load(f) : f
      end
    end
  
    def cache_method(method, find_args = {})
      controller = self.class_name.underscore.pluralize
      self.class_eval %Q{
        def self.#{method}(id, *args)
          check_cache("#{controller}/\#{id}-#{method}-", args) do
            self.find(id, #{find_args.inspect}).#{method}_without_cache(*args)
          end
        end
        
        def #{method}_with_cache(*args)
          self.class.check_cache("#{controller}/\#{id}-#{method}-", args) do
            #{method}_without_cache(*args)
          end
        end
        alias_method_chain :#{method}, :cache
      }
    end
  end
  
  def expire_cache(method = false)
    c = ActionController::Base.new
    return unless c.perform_caching
    controller = self.class.class_name.underscore.pluralize
    puts "expire #{controller} - id #{self.id}"
    unless method
      c.expire_fragment %r{#{controller}/#{self.id}-.*}
    else
      c.expire_fragment %r{#{controller}/#{self.id}-#{method}-.*}
    end
  end
end
